热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

iOS开发之路--仿网易抽屉效果

本文是IOS开发之路系列的第一篇,主要讲诉了如何仿网易新闻客户端实现抽屉效果,全部源代码都分享给大家,希望对大家有所帮助

最终效果图:


MainStoryBoard示意图:


BeyondViewController.h

//
// BeyondViewController.h
// 19_抽屉效果_仿网易
//
// Created by beyond on 14-8-1.
// Copyright (c) 2014年 com.beyond. All rights reserved.
//

#import 
#import "LeftTableViewControllerDelegate.h"



@interface BeyondViewController : UIViewController
// 左半边 (显示 的是栏目列表 )
@property (weak, nonatomic) IBOutlet UIView *leftView;
// 右半边 (显示 的是个人信息设置视图)
@property (weak, nonatomic) IBOutlet UIView *rightView;
// 最上面,最大的全屏的是主视图
@property (weak, nonatomic) IBOutlet UIView *mainView;



// 上面标题状态栏视图中的标题按钮 (网易的Logo图片和栏目的名称 水平排列)
@property (weak, nonatomic) IBOutlet UIButton *titleBtn;

// mainView的下半部分 是 正文的view,显示子栏目的view
@property (weak, nonatomic) IBOutlet UIView *contentView;



// pan 拽 手势处理
- (IBAction)panGesture:(UIPanGestureRecognizer *)sender;

// mainView的上半部分 标题状态栏视图中的左,右按钮
- (IBAction)btnClick:(UIButton *)sender;


@end

BeyondViewController.m

//
// BeyondViewController.m
// 19_抽屉效果_仿网易
//
// Created by beyond on 14-8-1.
// Copyright (c) 2014年 com.beyond. All rights reserved.
//

#import "BeyondViewController.h"
#import "LeftTableViewController.h"
#import "RightViewController.h"
#import "Column.h"
#import 
// 手势结束时的x
#define kEndX frame.origin.x
// 左view的宽度
#define kLeftWidth _leftView.frame.size.width

// 右view的宽度
#define kRightWidth _rightView.frame.size.width

// 对协议进行提前声明
@protocol LeftTableViewControllerDelegate ;

@interface BeyondViewController ()
{
  // 手指按下的时候,记住,mainView的起始x
  CGFloat _startX;
  
  // 成员变量,记住左边控制器的实例
  LeftTableViewController *_leftVC;
  // 成员变量,记住右边控制器的实例
  RightViewController *_rightVC;
  
  
  // 字典 ,记住所有实例化了 栏目的子控制器,避免每次都重新创建
  NSMutableDictionary *_columnViewControllers;
  
}

@end

@implementation BeyondViewController
// 隐藏状态栏
- (BOOL)prefersStatusBarHidden
{
  return YES;
}
- (void)viewDidLoad
{
  [super viewDidLoad];
  
  _titleBtn.backgroundColor = [UIColor clearColor];
  
  // 0 字典 ,记住所有实例化了 栏目的子控制器,避免每次都重新创建
  _columnViewCOntrollers= [NSMutableDictionary dictionary];
  
  // 0,设置导航条bar的背景 为网 易 红
  //[[UINavigationBar appearance] setBackgroundImage:[UIImage imageNamed:@"bg.png"] forBarMetrics:UIBarMetricsDefault];
  // 状态条颜色 改成默认的样式
  //[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;
  
  // 1,添加左边控制器的view到左边的view里面
  _leftVC = [[LeftTableViewController alloc]init];
  // 关键代码 为了拿到左边控制器的某一行被点击时候,对应的栏目数据模型对象,主控制器成为了左边控制器的代理,遵守了它定好的协议,实现了协议中的方法,从而拿到左边控制器被点击行号对应的数据模型对象
  _leftVC.delegate = self;
  _leftVC.view.frame = self.leftView.bounds;
  
  [self.leftView addSubview:_leftVC.view];
  
  // 2,同理,添加右边控制器的view到右边的view里面
	_rightVC = [[RightViewController alloc]init];
  
  _rightVC.view.frame = self.rightView.bounds;
  
  [self.rightView addSubview:_rightVC.view];

  // 3,第一次加载时候,就就应该显示新闻 子栏目的控制器到导航控制器,再将导航控制器的view添加到 mainView里面
  [self firstLoading];
  
  
}


// 自定义方法,第一次加载时候,就就应该显示新闻 子栏目的控制器到导航控制器,再将导航控制器的view添加到 mainView里面
- (void)firstLoading
{
  Column * column = [Column columnNamed:@"新闻" imgName:@"news.png" className:@"NewsViewController"];
  
  // 仅需手动调用一个 LeftViewController的代理 方法,leftTableViewRowClicked,传递一个新闻 子栏目即可
  [self leftTableViewRowClicked:column];
}

// pan 拽 手势处理
- (IBAction)panGesture:(UIPanGestureRecognizer *)sender
{
  
  // 如果是刚按下的状态,则记住,mainView的起始x
  if (UIGestureRecognizerStateBegan == sender.state) {
    _startX = self.mainView.frame.origin.x;
  }
  
  
  // 平移拖动的距离
  CGPoint delta = [sender translationInView:_mainView];
  
  CGRect frame = self.mainView.frame;
  
  // 计算新的x值,并做健壮性判断
  kEndX = _startX + delta.x;
  
  // 1,限制最大拖动范围
  
  if (kEndX >= kLeftWidth) {
    kEndX = kLeftWidth;
  }
  if (kEndX <= - kRightWidth) {
    kEndX = - kRightWidth;
  }
  // 2,由于 左view和右view在重叠,所以要隐藏其中的一个
  if (kEndX > 0) {
    // NSLog(@"--调用频率相当高--");
    _rightView.hidden = YES;
    _leftView.hidden = NO;
  } else {
    _rightView.hidden = NO;
    _leftView.hidden = YES;
  }
  
  
  if (UIGestureRecognizerStateEnded == sender.state) {
    
    // 手势结束的时候,需进行robust判断
    
    // 2,分析end松手时候,的位置x,决定展开到什么程度
/*
    // 2.1 如果只向右拖了一点点,小于 1/2 的左view的宽度,则归0
    if (kEndX <0.5*kLeftWidth && kEndX >= 0) {
      kEndX = 0;
    }else if (kEndX >= 0.5*kLeftWidth && kEndX <= kLeftWidth) {
      // 2.2 如果向右拖一大半了,大于 1/2 的左view的宽度,虽然还没到位,也可以认为是到位了
      kEndX = kLeftWidth;
    }else if (kEndX > - 0.5*kRightWidth && kEndX <= 0) {
      // 2.3 如果只向左拖了一点点,小于 1/2 的右view的宽度,则归0
      kEndX = 0;
    }else if (kEndX <= - 0.5*kRightWidth) {
      // 2.4 如果向左拖一大半了,大于 1/2 的右view的宽度,虽然还没到位,也可以认为是到位了
      kEndX = - kRightWidth;
    }
*/
    
    
    // 第2种判断方式
    // 起始为0,delta.x大于0 代表向右滑动
    if (_startX == 0 && delta.x >0) {
      kEndX = kLeftWidth;
    }else if (_startX == 0 && delta.x <0){
      // 起始为0,delta.x小于0 代表向左滑动
      kEndX = - kRightWidth;
    }else if (_startX == kLeftWidth && delta.x <0){
      // 起始为kLeftWidth,delta.x小于0 代表向左滑动
      kEndX =0;
    }else if (_startX == - kRightWidth && delta.x > 0){
      // 起始为- kRightWidth,delta.x大于0 代表向右滑动
      kEndX = 0;
    }
    

    
  }
  
  // 最后,才设置mainView的新的frame
  [UIView animateWithDuration:0.2 animations:^{
        self.mainView.frame=frame;
  }];
  
  
  
  // 最后,为mainView所在的图层 添加阴影效果
  [self addShadowFormainViewWithEndX:kEndX];
  
}

// 自定义方法,为mainView所在的图层 添加阴影效果 (调用频率相当高)
- (void)addShadowFormainViewWithEndX:(CGFloat)endX
{
  // 1,点击工程,加号,导入第3方框架 #import 
  
  // 2,拿到mainView所在的图层,设置阴影 参数
 
  // NSLog(@"调用频率很高---");
  _mainView.layer.shadowColor = [UIColor blackColor].CGColor;
  _mainView.layer.shadowOpacity = 0.5;
  if (endX >= 0) {
    _mainView.layer.shadowOffset = CGSizeMake(-5, 0);
  } else {
    _mainView.layer.shadowOffset = CGSizeMake(5, 0);
  }
  
}

// 单击按钮,也一样可以展开 左右侧边栏
- (IBAction)btnClick:(UIButton *)sender
{
  // 定义一个临时变量
  CGFloat startX = _mainView.frame.origin.x;
  
  
  // 先为mainView所在的图层 添加阴影效果
  [self addShadowFormainViewWithEndX:sender.tag == 1&#63;1:-1];
  
  
  // 定义一个临时变量
  CGFloat tempEndX = 0;
  // 左边的按钮被单击
  if (1 == sender.tag) {
    // 隐藏右半边
    _leftView.hidden = NO;
    _rightView.hidden = YES;
    
    if (startX == 0) {
      tempEndX = kLeftWidth;
    }else if (startX == kLeftWidth){
      tempEndX = 0;
    }
  } else {
    // 单击右边按钮, 隐藏左半边
    _leftView.hidden = YES;
    _rightView.hidden = NO;
    if (startX == 0) {
      tempEndX = - kRightWidth;
    }else if (startX == - kRightWidth){
      tempEndX = 0;
    }
  }
  // 最后才设置mainView的x,调用抽取出来的公共代码,设置mainView的x,参数是endX
  [self setmainViewX:tempEndX];
  
  

}


// 抽取出来的公共代码,设置mainView的x,参数是endX
- (void)setmainViewX:(CGFloat)endX
{
  CGRect frame = self.mainView.frame;
  frame.origin.x = endX;
  [UIView animateWithDuration:0.2 animations:^{
    self.mainView.frame=frame;
  }];
  
}



// 最关键的方法,左边控制器的代理 方法,当前左边控制器中的某一行被点击的时候 会调用
- (void)leftTableViewRowClicked:(id)columnSelected
{
  Column *column = (Column *)columnSelected;
  // 1,关闭左边的控制=======================
  // 调用抽取出来的公共代码,设置mainView的x,参数是endX
  [self setmainViewX:0];
  
  
  
  // 2,更改标题按钮上面的文字
  _titleBtn.titleLabel.text = column.columnName;
  
  // 根据栏目数据模型中的类名,实例化对应栏目的控制器,并且将其设置为导航控制器的根控制器,最后将导航控制器的view添加到mainView中,目的是方便设置导航条,以及,各控制器的跳转
  
  
  // 2,从缓存字典中取,如果子控制器字典有曾经创建过的子控制器,直接取出来用
  UIViewController *columnVC = _columnViewControllers[column.columnClassName];
  // 如果子控制器字典中没有保存过该栏目的控制器,才要创建子控制器
  if (columnVC == nil) {
    Class c = NSClassFromString(column.columnClassName);
    columnVC = [[c alloc]init];
    // 并且一定要将其放到 子控制器字典里面,存起来
    [_columnViewControllers setObject:columnVC forKey:column.columnClassName];
  }
    
  // 4,移除contentView中的正在显示的旧的子view
  if (_contentView.subviews.count > 0) {
    UIView *oldView = [_contentView subviews][0];
    [oldView removeFromSuperview];
  }

  // 5,最后将子控制器的view添加到contentView中,显示
  columnVC.view.frame = _contentView.bounds;
  [self.contentView addSubview:columnVC.view];
  NSLog(@"%@",self.contentView);
  // 在添加到mainView之前 ,先得到mainView导航控制器的子控制器,并将其移除(如果有的话),然后才将新的栏目对应的子控制器添加到导航控制器容器中,注意,这儿可以用字典 记住 所有的已经实例化出来 的栏目子控制器,这样就避免每次都alloc创建新的栏目子控制器,而是只需要根据类名,从字典取出上一次实例化了的同一栏目的子控制器即可
  
}
@end

栏目数据模型Column.h

//
// Column.h
// 19_抽屉效果_仿网易
//
// Created by beyond on 14-8-1.
// Copyright (c) 2014年 com.beyond. All rights reserved.
//

#import 

// 数据模型 代表一个栏目
@interface Column : NSObject
// 栏目名称
@property (nonatomic,copy)NSString *columnName;
// 栏目图片名称
@property (nonatomic,copy)NSString *columnImgName;
// 栏目对应的控制器的类名
@property (nonatomic,copy)NSString *columnClassName;

// UI控件用weak,字符串用copy,其他对象用strong

// 提供一个类方法,即构造函数,返回封装好数据的对象(返回id亦可)
+ (Column *)columnNamed:(NSString *)columnName imgName:(NSString*)columnImgName className:(NSString *)columnClassName;
@end

栏目数据模型Column.m

//
// Column.m
// 19_抽屉效果_仿网易
//
// Created by beyond on 14-8-1.
// Copyright (c) 2014年 com.beyond. All rights reserved.
// 数据模型 代表一条栏目

#import "Column.h"

@implementation Column

// 返回一个包含了 栏目对应控制器名字的 对象实例
+ (Column *)columnNamed:(NSString *)columnName imgName:(NSString *)columnImgName className:(NSString *)columnClassName
{
  // 为了兼容子类 使用self
  Column *column = [[self alloc]init];
  column.columnName = columnName;
  column.columnImgName = columnImgName;
  column.columnClassName = columnClassName;
  return column;
}

@end

左边控制器定义好的协议LeftTableViewControllerDelegate.h

//
// LeftTableViewControllerDelegate.h
// 19_抽屉效果_仿网易
//
// Created by beyond on 14-8-1.
// Copyright (c) 2014年 com.beyond. All rights reserved.
//

#import 
#import "Column.h"
// 左边控制器 定义的代理/协议 它通过调用自己的成员属性(即代理)的该方法,将数据传递出去(给它的代理去使用) (其实 是主控制器想要数据,所以主控制器在实例化左边控制器的时候,要设置左边控制器对应的代理 为 主控制器 自身)
@protocol LeftTableViewControllerDelegate 


- (void)leftTableViewRowClicked:(Column *)columnSelected;
@end

LeftTableViewController.h

//
// LeftTableViewController.h
// 19_抽屉效果_仿网易
//
// Created by beyond on 14-8-1.
// Copyright (c) 2014年 com.beyond. All rights reserved.
//

#import 
// 对协议进行提前声明
@protocol LeftTableViewControllerDelegate;


@interface LeftTableViewController : UITableViewController
// 代理 用weak,防止循环问题,可以是任意类型,但必须遵守协议
@property (nonatomic,weak) id delegate;
@end

LeftTableViewController.m

//
//  LeftTableViewController.m
//  19_抽屉效果_仿网易
//
//  Created by beyond on 14-8-1.
//  Copyright (c) 2014年 com.beyond. All rights reserved.
//
#import "LeftTableViewController.h"
#import "Column.h"
#import "LeftTableViewControllerDelegate.h"
@interface LeftTableViewController ()
{
    // 栏目数组,保存的是左边栏目列表中的所有栏目对象
    NSArray *_arr;
}
@end
@implementation LeftTableViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    // 新闻 栏目
    Column *newsColumn = [Column columnNamed:@"新闻" imgName:@"news.png" className:@"NewsViewController"];
    // 图片 栏目
    Column *picColumn = [Column columnNamed:@"图片" imgName:@"pic.png" className:@"PicViewController"];
    // 图片 栏目
    Column *commentColumn = [Column columnNamed:@"跟帖" imgName:@"comment.png" className:@"CommentViewController"];
    // 以后要添加栏目,只要改这里就可以了
   
   
    // 将栏目对象,一次性全添加到不可变数组中
    _arr = @[newsColumn,picColumn,commentColumn];
   
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return _arr.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellID = @"leftVC";
    // 下面这个dequeue只能用于storyboard或xib中
    // UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];
    //
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
   
    if (cell == nil) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
    }
   
    // 设置独一无二的数据
    Column *column = _arr[indexPath.row];
    cell.textLabel.text = column.columnName;
    cell.imageView.image = [UIImage imageNamed:column.columnImgName];
    return cell;
}

// 点击一行时,主控制中的主视图必须展示相应栏目的内容,因此,必须实例化对应点击的行的栏目控制器,并用添加到导航控制器,调用代理 的方法传递数据给代理 使用,此处的代理 其实就是 主控制器
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 先取消默认的点击 高亮的颜色
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    // 取出对应行的数据模型(栏目)
    Column *column = _arr[indexPath.row];
   
    if ([self.delegate respondsToSelector:@selector(leftTableViewRowClicked:)]) {
       
        // 传递数据给主控制器 BeyondViewController,通过代理
        // 关键代码~
        [self.delegate leftTableViewRowClicked:column];
    }
   
   
}
@end

RightViewController.xib


NewsViewController.xib


PicViewController.xib


CommentViewController.xib



推荐阅读
  • Prim算法在处理稠密图时表现出色,尤其适用于边数远多于顶点数的情形。传统实现的时间复杂度为 \(O(n^2)\),但通过引入优先队列进行优化,可以在点数为 \(m\)、边数为 \(n\) 的情况下显著降低时间复杂度,提高算法效率。这种优化方法不仅能够加速最小生成树的构建过程,还能在大规模数据集上保持良好的性能表现。 ... [详细]
  • 从零起步:使用IntelliJ IDEA搭建Spring Boot应用的详细指南
    从零起步:使用IntelliJ IDEA搭建Spring Boot应用的详细指南 ... [详细]
  • BZOJ4240 Gym 102082G:贪心算法与树状数组的综合应用
    BZOJ4240 Gym 102082G 题目 "有趣的家庭菜园" 结合了贪心算法和树状数组的应用,旨在解决在有限时间和内存限制下高效处理复杂数据结构的问题。通过巧妙地运用贪心策略和树状数组,该题目能够在 10 秒的时间限制和 256MB 的内存限制内,有效处理大量输入数据,实现高性能的解决方案。提交次数为 756 次,成功解决次数为 349 次,体现了该题目的挑战性和实际应用价值。 ... [详细]
  • Spring Boot 实战(一):基础的CRUD操作详解
    在《Spring Boot 实战(一)》中,详细介绍了基础的CRUD操作,涵盖创建、读取、更新和删除等核心功能,适合初学者快速掌握Spring Boot框架的应用开发技巧。 ... [详细]
  • PHP中元素的计量单位是什么? ... [详细]
  • 本文作为“实现简易版Spring系列”的第五篇,继前文深入探讨了Spring框架的核心技术之一——控制反转(IoC)之后,将重点转向另一个关键技术——面向切面编程(AOP)。对于使用Spring框架进行开发的开发者来说,AOP是一个不可或缺的概念。了解AOP的背景及其基本原理,对于掌握这一技术至关重要。本文将通过具体示例,详细解析AOP的实现机制,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 本文深入探讨了 MXOTDLL.dll 在 C# 环境中的应用与优化策略。针对近期公司从某生物技术供应商采购的指纹识别设备,该设备提供的 DLL 文件是用 C 语言编写的。为了更好地集成到现有的 C# 系统中,我们对原生的 C 语言 DLL 进行了封装,并利用 C# 的互操作性功能实现了高效调用。此外,文章还详细分析了在实际应用中可能遇到的性能瓶颈,并提出了一系列优化措施,以确保系统的稳定性和高效运行。 ... [详细]
  • 本题库精选了Java核心知识点的练习题,旨在帮助学习者巩固和检验对Java理论基础的掌握。其中,选择题部分涵盖了访问控制权限等关键概念,例如,Java语言中仅允许子类或同一包内的类访问的访问权限为protected。此外,题库还包括其他重要知识点,如异常处理、多线程、集合框架等,全面覆盖Java编程的核心内容。 ... [详细]
  • HBase在金融大数据迁移中的应用与挑战
    随着最后一台设备的下线,标志着超过10PB的HBase数据迁移项目顺利完成。目前,新的集群已在新机房稳定运行超过两个月,监控数据显示,新集群的查询响应时间显著降低,系统稳定性大幅提升。此外,数据消费的波动也变得更加平滑,整体性能得到了显著优化。 ... [详细]
  • 本文探讨了在Lumen框架中实现自定义表单验证功能的方法与挑战。Lumen的表单验证机制默认返回无状态的JSON格式API响应,这给初学者带来了一定的难度。通过深入研究Validate类,作者分享了如何有效配置和使用自定义验证规则,以提升表单数据的准确性和安全性。 ... [详细]
  • 题目描述:小K不幸被LL邪教洗脑,洗脑程度之深使他决定彻底脱离这个邪教。在最终离开前,他计划再进行一次亚瑟王游戏。作为最后一战,他希望这次游戏能够尽善尽美。众所周知,亚瑟王游戏的结果很大程度上取决于运气,但通过合理的策略和算法优化,可以提高获胜的概率。本文将详细解析洛谷P3239 [HNOI2015] 亚瑟王问题,并提供具体的算法实现方法,帮助读者更好地理解和应用相关技术。 ... [详细]
  • 2019年后蚂蚁集团与拼多多面试经验详述与深度剖析
    2019年后蚂蚁集团与拼多多面试经验详述与深度剖析 ... [详细]
  • 本文深入探讨了 HTML 中的 `margin` 属性,详细解析了其基本特性和应用场景。文章不仅介绍了 `margin` 的基本概念,还重点讨论了垂直外边距合并现象,并分析了 `margin` 在块级元素与内联元素中的不同表现。通过实例和代码示例,帮助读者全面理解 `margin` 的使用技巧和常见问题。 ... [详细]
  • POJ 1696: 空间蚂蚁算法优化与分析
    针对 POJ 1696 的空间蚂蚁算法进行了深入的优化与分析。本研究通过改进算法的时间复杂度和空间复杂度,显著提升了算法的效率。实验结果表明,优化后的算法在处理大规模数据时表现优异,能够有效减少计算时间和内存消耗。此外,我们还对算法的收敛性和稳定性进行了详细探讨,为实际应用提供了可靠的理论支持。 ... [详细]
  • Go语言中的高效排序与搜索算法解析
    在探讨Go语言中高效的排序与搜索算法时,本文深入分析了Go语言提供的内置排序功能及其优化策略。通过实例代码,详细讲解了如何利用Go语言的标准库实现快速、高效的排序和搜索操作,为开发者提供了实用的编程指导。 ... [详细]
author-avatar
BigUncle
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有